package mediaCoreLib.managers
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.media.ID3Info;
	
	import mediaCoreLib.core.PlaylistObject;
	import mediaCoreLib.events.FadeEvent;
	
	import mx.utils.ArrayUtil;
	
	public class PlaylistManager extends EventDispatcher
	{
		///////////////////////////////////////////////////////
		//	STATIC
		///////////////////////////////////////////////////////

		/**
		 * @private
		 */
		static private var _cachedMedia:Object = {}; //storage point for prev downloaded data
		
		/**
		 * The default length of time (in milliseconds) to move during 
		 * scrub (fast-forward/rewind).  Used in initializing scrubLength.
		 */
		static private const DEFAULT_SCRUB_LENGTH : int = 5000;
		
		///////////////////////////////////////////////////////
		//	CONSOLE
		///////////////////////////////////////////////////////
		
		private var _isPlayClicked:Boolean = false; //this does not mean we are playing, only that the user tried to initiate playing.
		
		//TODO: add playlist as param
		public function play (doFade:Boolean = true):void
		{
			_isPlayClicked = true;
			
			if (!_currentPlaylistObj && mediaQueue[index])
				_currentPlaylistObj = mediaQueue[index];
			
			if (_currentPlaylistObj.isLoaded)
				_currentPlaylistObj.play(doFade);

			// ID3 - should put this into its own handler with an id3 event or something
			//id3Info = _currentPlaylistObj.id3Info;
		}
		
		public function pause ():void
		{
			_isPlayClicked = false;
			
			// make sure there is an object to pause
			if (_currentPlaylistObj)
			{
				_currentPlaylistObj.pause();
				setIsPlaying(_currentPlaylistObj.isPlaying);
			}
			
			else
			{
				// decide whether or not to handle pause if there is nothing created
			}
		}
		
		public function stop ():void
		{
			_isPlayClicked = false;
			
			if (_currentPlaylistObj)
			{
				_currentPlaylistObj.stop();
				setIsPlaying(_currentPlaylistObj.isPlaying);
			}
			
			index = 0;
			//_currentPlaylistObj.removeEventListener("fadeOutComplete", onFadeOutComplete);
		}
		
		// fast forward
		public function scrubForward ():void
		{
			pause();
			_currentPlaylistObj.currentPosition += scrubLength;
			play(false);
			
		}
		
		// rewind
		public function scrubBackward ():void
		{
			pause();
			_currentPlaylistObj.currentPosition -= scrubLength;
			play(false);
		}
		
/* ****************
* TEST FUNCTION FOR FADE... DON'T FUCK WITH THE INDENTION! - haha you ass!!! too funny.
****************/
public function stopWithFade ():void
{
	if (_currentPlaylistObj)
	{
		_currentPlaylistObj.startFadeOut();
	}	
}		

		///////////////////////////////////////////////////////
		//	USE CROSSFADE
		///////////////////////////////////////////////////////
		
		private var _useCrossfade:Boolean = true;
		
		public function get useCrossfade ():Boolean
		{
			return _useCrossfade;
		}
		
		public function set useCrossfade (value:Boolean):void
		{
			if (value != _useCrossfade)
				_useCrossfade = value;
		}
		
		///////////////////////////////////////////////////////
		//	IS PLAYING
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private var _isPlaying:Boolean = false;
		
		[Bindable("isPlayingChanged")]
		public function get isPlaying ():Boolean
		{
			return _isPlaying;
		}
		
		/**
		 * @private
		 * 
		 * An internal setter
		 */
		private function setIsPlaying (value:Boolean):void
		{
			if (_isPlaying != value)
			{
				_isPlaying = value;
				dispatchEvent(new Event("isPlayingChanged"));
			}
		}
	
		///////////////////////////////////////////////////////
		//	FADE IN LENGTH
		///////////////////////////////////////////////////////
			
		/**
		 * @private
		 */
		private var _fadeInLength:Number = 1000;
		
		[Bindable("fadeInLengthChanged")]
		public function get fadeInLength ():Number
		{
			return _fadeInLength;
		}
		
		public function set fadeInLength (value:Number):void
		{
			if (_fadeInLength != value)
			{
				_fadeInLength = value;
				
				var obj:PlaylistObject;
				for each (obj in mediaQueue)
					obj.fadeInLength = value;
				
				dispatchEvent(new Event("fadeInLengthChanged"));
			}
		}
		
		///////////////////////////////////////////////////////
		//	FADE OUT LENGTH
		///////////////////////////////////////////////////////
			
		/**
		 * @private
		 */
		private var _fadeOutLength:Number = 1000;
		
		[Bindable("fadeOutLengthChanged")]
		public function get fadeOutLength ():Number
		{
			return _fadeOutLength;
		}
		
		public function set fadeOutLength (value:Number):void
		{
			if (_fadeOutLength != value)
			{
				_fadeOutLength = value;
				
				var obj:PlaylistObject;
				for each (obj in mediaQueue)
					obj.fadeOutLength = value;
				
				dispatchEvent(new Event("fadeOutLengthChanged"));
			}
		}
		///////////////////////////////////////////////////////
		//	PLAYLIST
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private var _pl:Array = [];
		
		[Bindable("playlistChanged")]
		public function get playlist ():Array
		{
			return _pl;
		}
		
		public function set playlist (value:Array):void
		{
			if (_pl.toString() != value.toString())
			{
				_pl = value;
				index = 0;
				
				dispatchEvent(new Event("playlistChanged"));
				
				mediaRetrieved = false;
				getPlaylistMedia(_pl);
			}
		}
		
		///////////////////////////////////////////////////////
		//	DOWNLOAD MEDIA
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private var _currentPlaylistObj:PlaylistObject;
		
		/**
		 * @private
		 */
		private var _previousPlaylistObj:PlaylistObject;
		
		protected var mediaQueue:Array = []; //contains objects PlaylistObjects
		
		protected var mediaRetrieved:Boolean = false;
		
		protected function getPlaylistMedia (urls:Array):void
		{
			mediaQueue = [];
			
			var obj:PlaylistObject;
			var url:String
			for each (url in urls)
			{
				//check to see if media exists in dict if not, retrieve it
				if (_cachedMedia[url] == null)
				{
					obj = new PlaylistObject();
					
					//loading
					obj.addEventListener(Event.COMPLETE, playlistObject_completeHandler);
					obj.addEventListener(IOErrorEvent.IO_ERROR, playlistObject_IOErrorHandler);
					
					//play events
					obj.addEventListener(Event.OPEN, playlistObject_openHandler);
					obj.addEventListener(FadeEvent.FADE_IN_COMPLETE, playlistObject_fadeInCompleteHandler);
					obj.addEventListener(FadeEvent.FADE_OUT_START, playlistObject_fadeOutStartHandler);
					obj.addEventListener(Event.SOUND_COMPLETE, playlistObject_soundCompleteHandler);
					
					obj.url = url;
					
					_cachedMedia[url] = obj;
				}
				
				else
					obj = _cachedMedia[url];
				
				//set some simple props
				obj.fadeInLength = fadeInLength;
				obj.fadeOutLength = fadeOutLength;
				
				mediaQueue.push(obj);
			}
			
			PlaylistObject(mediaQueue[index]).load();
			
			mediaRetrieved = true;
		}
		
		///////////////////////////////////////////////////////
		//	PLAYLIST OJBECT EVT HANDLERS
		///////////////////////////////////////////////////////
		
		/**
		 * Handler for when a playlistObject has successfully loaded.
		 * 
		 * @param evt The event signifying successfully loaded media.
		 */
		protected function playlistObject_completeHandler (evt:Event):void
		{
			var obj:PlaylistObject = PlaylistObject(evt.target);
			obj.removeEventListener(Event.COMPLETE, playlistObject_completeHandler);
			obj.removeEventListener(IOErrorEvent.IO_ERROR, playlistObject_IOErrorHandler);
			
			if (_isPlayClicked && obj == mediaQueue[index])
			{
				if (obj.isLoaded)
					obj.play(useCrossfade);
			}
			
			loadNext(obj);
		}
		
		/**
		 * Handler for when a playlistObject has unsuccessfully loaded.
		 * 
		 * @param evt The event signifying unsuccessfully loaded media.
		 */
		protected function playlistObject_IOErrorHandler (evt:IOErrorEvent):void
		{
			//uh oh we had an issue.  We kick this one out of our media queue and if needed throw an indication
			var obj:PlaylistObject = PlaylistObject(evt.target);
			obj.removeEventListener(Event.COMPLETE, playlistObject_completeHandler);
			obj.removeEventListener(IOErrorEvent.IO_ERROR, playlistObject_IOErrorHandler);
			
			mediaQueue[obj.url] = null; //does this work?  I thought this was an array so we need to find the index right?
		}
		
		protected function playlistObject_openHandler (evt:Event):void
		{
			var playing:Boolean = PlaylistObject(evt.target).isPlaying;
			setIsPlaying(playing);	
				
			// ID3 TEST
			//id3Info = _currentPlaylistObj.id3Info;
		}
		
		protected function playlistObject_fadeInCompleteHandler (evt:FadeEvent):void
		{
			//for now do nothing
		}
		
		protected function playlistObject_fadeOutStartHandler (evt:FadeEvent):void
		{
			if (useCrossfade)
				handlePlaylistObjectEndEvent();
		}
		
		/**
		 * @private
		 */
		protected function playlistObject_soundCompleteHandler (evt:Event):void
		{
			if (!useCrossfade)
				handlePlaylistObjectEndEvent();
		}
		
		/**
		 * @private
		 */
		private function handlePlaylistObjectEndEvent ():void
		{
			//need to advance the index back to the beginning
			if (index == mediaQueue.length - 1)
			{
				if (!useCrossfade)
					stop();
				
				index = 0;
				
				if (repeatAll)
					play();
			}
			
			//we are just going to the next item in the playlist
			else
				index++;
		}
		
		///////////////////////////////////////////////////////
		//	ALLOW LOOP
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private var _repeatAll:Boolean = false;
		
		[Bindable("repeatAllChanged")]
		public function get repeatAll ():Boolean
		{
			return _repeatAll;
		}
		
		public function set repeatAll (value:Boolean):void
		{
			if (_repeatAll != value)
			{
				_repeatAll = value;
				
				dispatchEvent(new Event("repeatAllChanged"));
			}
		}
	
		///////////////////////////////////////////////////////
		//	VOLUME
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private var _volume:Number = 1;
		
		[Bindable("volumeChanged")]
		public function get volume ():Number
		{
			return _volume;
		}
		
		public function set volume (value:Number):void
		{
			if (_volume != value)
			{
				_volume = value;
				
				dispatchEvent(new Event("volumeChanged"));
			}
		}
		
		///////////////////////////////////////////////////////
		//	SCRUB LENGTH
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private var _scrubLength:int = DEFAULT_SCRUB_LENGTH;

		[Bindable("scrubLengthChanged")]
		public function get scrubLength ():int
		{
			return _scrubLength;
		}
		
		public function set scrubLength (value:int):void
		{
			if (_scrubLength != value)
			{
				_scrubLength = value;
				
				dispatchEvent(new Event("scrubLengthChanged"));
			}
		}	
			
		///////////////////////////////////////////////////////
		//	INDEX
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private var _index:uint;
		//private var _indexChanged:Boolean = false;
		
		[Bindable("indexChanged")]
		public function get index ():uint
		{
			return _index;
		}
		
		public function set index (value:uint):void
		{
			if (_index != value)
			{
				_index = value;
				
				//check to make sure we are not out of bounds
				if (_index >= mediaQueue.length)
				{
					// go back to the beginning of the playlist if repeatAll is selected
					// should this check for length of 1 instead?? -MO
					if (mediaQueue.length == 0 || repeatAll)
						_index = 0;
						
					else
						_index = mediaQueue.length - 1;
				}
				
				handleIndexChange();				
				dispatchEvent(new Event("indexChanged"));
			}
		}
		
		private function handleIndexChange ():void
		{
			//check to see if we are playing & all media has been queued for retreival
			if (_isPlayClicked/* isPlaying */ && mediaRetrieved)
			{	
				// CROSSFADE LOGIC
				if (_currentPlaylistObj && !_useCrossfade)
					_currentPlaylistObj.stop();
					
				else if (_currentPlaylistObj && _useCrossfade)
					_currentPlaylistObj.startFadeOut(true);
				
				_previousPlaylistObj = _currentPlaylistObj;
				
				_currentPlaylistObj = mediaQueue[index];
				_currentPlaylistObj.load();
				_currentPlaylistObj.play();
			}
			
			//otherwise we just update the next available obj
			else
				_currentPlaylistObj = mediaQueue[index];
		}
		
		
		///////////////////////////////////////////////////////
		//	ID3 INFO
		///////////////////////////////////////////////////////
		
		private var _id3Info:ID3Info;
		
		[Bindable("id3InfoChanged")]
		public function get id3Info():ID3Info
		{
			return _id3Info;
		}
		public function set id3Info(value:ID3Info):void
		{
			_id3Info = value;
			dispatchEvent(new Event("id3InfoChanged"));
		}
		///////////////////////////////////////////////////////
		//	ARRAY UTIL
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		private function getNext (a:Array, currentIndex:int):Object
		{
			if (currentIndex < a.length - 1)
				return a[currentIndex + 1];
				
			else
				return a[0];
		}
		
		/**
		 * @private
		 */
		private function loadNext (currentItem:PlaylistObject):void
		{
			var i:int = ArrayUtil.getItemIndex(currentItem, mediaQueue);
			var next:PlaylistObject = PlaylistObject(getNext(mediaQueue, i));
			if (!next.isLoaded)
				next.load();
		}
		
		///////////////////////////////////////////////////////
		//	SINGLETON
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 */
		static private var _instance:PlaylistManager;
		
		static public function getInstance ():PlaylistManager
		{
			if (!_instance)
				_instance = new PlaylistManager();
				
			return _instance;
		}
		
		/**
		 * @private
		 */
		function PlaylistManager ()
		{
			super();
		}
	}
}